package com.yl.request.common.glaencrypt;

import com.yl.request.common.glaencrypt.enums.CryptLevel;
import com.yl.request.common.glaencrypt.enums.NetworkType;
import com.yl.request.common.glaencrypt.sm2.SM2Utils;
import org.bouncycastle.crypto.io.InvalidCipherTextIOException;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.*;
import java.util.Arrays;
import java.util.UUID;

public class DxpEncryptInputStream extends FilterInputStream {
    private static final String DXP_FILE_MAGIC = "CZBDXP\u0000\u0000";
    private final int DXP_CHUNK_SIZE = 65536;
    private final int DXP_FILE_HEAD_SIZE = 137;
    private final int DXP_BLOCK_SIZE = 16;
    private final Cipher cipher;
    private final byte[] inputBuffer = new byte[65536];
    private boolean finalized = false;
    private byte[] buf;
    private int maxBuf;
    private int bufOff;
    private boolean headHandled = false;
    private final long length;
    private final String flag;
    private final byte[] keyBytes;
    private final String sm2PublicKey;

    protected DxpEncryptInputStream(InputStream input, long length, CryptLevel cryptLevel, NetworkType networkType, String sysCode, String sm2PublicKey) throws Exception {
        super(input);
        byte[] id = UUID.randomUUID().toString().replaceAll("-", "").getBytes();
        this.keyBytes = Arrays.copyOfRange(id, 8, 24);
        this.cipher = Cipher.getInstance("SM4/ECB/PKCS7Padding", "BC");
        Key sm4Key = new SecretKeySpec(this.keyBytes, "SM4");
        this.cipher.init(1, sm4Key);
        this.length = length;
        this.flag = cryptLevel.Level() + networkType.Type() + sysCode;
        this.sm2PublicKey = sm2PublicKey;
    }

    private int nextChunk() throws IOException {
        if (this.finalized) {
            return -1;
        } else {
            this.bufOff = 0;
            this.maxBuf = 0;
            int off = 0;
            int len = 65536;
            int size = 0;

            while (true) {
                int read;
                do {
                    if (this.maxBuf != 0) {
                        return this.maxBuf;
                    }

                    read = this.in.read(this.inputBuffer, off, len);
                    if (read != -1) {
                        off += read;
                        len -= read;
                        size += read;
                    }
                } while (read != -1 && size != 65536);

                if (read == -1 && size == 0) {
                    this.buf = this.finaliseCipher();
                    if (this.buf != null && this.buf.length != 0) {
                        this.maxBuf = this.buf.length;
                        return this.maxBuf;
                    }

                    return -1;
                }

                try {
                    this.buf = this.cipher.doFinal(this.inputBuffer, 0, size);
                } catch (GeneralSecurityException var10) {
                    throw new InvalidCipherTextIOException("Error cipher", var10);
                }

                if (!this.headHandled) {
                    try {
                        byte[] headChunkBuf = new byte[137 + this.buf.length];
                        System.arraycopy("CZBDXP\u0000\u0000".getBytes(), 0, headChunkBuf, 0, 8);
                        System.arraycopy(this.flag.getBytes(), 0, headChunkBuf, 8, 4);
                        String fileSize = String.format("%012d", this.length);
                        System.arraycopy(fileSize.getBytes(), 0, headChunkBuf, 12, 12);
                        PublicKey pub = SM2Utils.generatePublicKey(Base64.decode(this.sm2PublicKey));
                        byte[] encryptKeyBytes = SM2Utils.encrypt(pub, this.keyBytes);
                        System.arraycopy(encryptKeyBytes, 0, headChunkBuf, 24, 113);
                        System.arraycopy(this.buf, 0, headChunkBuf, 137, this.buf.length);
                        this.buf = headChunkBuf;
                        this.headHandled = true;
                    } catch (Exception var9) {
                        throw new InvalidCipherTextIOException("Build file head error", var9);
                    }
                }

                if (this.buf != null) {
                    this.maxBuf = this.buf.length;
                }
            }
        }
    }

    private byte[] finaliseCipher() throws InvalidCipherTextIOException {
        if (!this.finalized) {
            this.finalized = true;
        }

        return null;
    }

    @Override
    public int read() throws IOException {
        return this.bufOff >= this.maxBuf && this.nextChunk() < 0 ? -1 : this.buf[this.bufOff++] & 255;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if (this.bufOff >= this.maxBuf && this.nextChunk() < 0) {
            return -1;
        } else {
            int toSupply = Math.min(len, this.available());
            System.arraycopy(this.buf, this.bufOff, b, off, toSupply);
            this.bufOff += toSupply;
            return toSupply;
        }
    }

    @Override
    public long skip(long n) throws IOException {
        if (n <= 0L) {
            return 0L;
        } else {
            int skip = (int) Math.min(n, this.available());
            this.bufOff += skip;
            return skip;
        }
    }

    @Override
    public int available() throws IOException {
        return this.maxBuf - this.bufOff;
    }

    @Override
    public void close() throws IOException {
        try {
            this.in.close();
        } finally {
            if (!this.finalized) {
                this.finaliseCipher();
            }

        }

        this.maxBuf = this.bufOff = 0;
    }

    @Override
    public void mark(int readlimit) {
    }

    @Override
    public void reset() throws IOException {
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    public long getCryptLength() {
        return this.computeCryptLength(this.length);
    }

    private long computeCryptLength(long length) {
        long dataChunks = length / 65536L;
        long dataResidue = length % 65536L;
        long blocks = dataResidue / 16L + 1L;
        long encryptedSize = 137L + dataChunks * 65552L + blocks * 16L;
        return encryptedSize;
    }
}
